package cn.admin.remoting.util;

import java.lang.ref.SoftReference;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;

public class ConnectionUrl {

    static ConcurrentHashMap<String, SoftReference<ConnectionUrl>> parsedUrls = new ConcurrentHashMap<>();
    static volatile boolean isCollected = false;
    private final String originalURL;
    private final String formatedURL;
    private final int hashcode;
    private String targetAddr;
    private String targetAddrIP;
    private int targetPort;
    private TreeMap<String,String> connectProps = new TreeMap<>();

    private ConnectionUrl(String url) {

        if (url == null) {
            throw new IllegalArgumentException("传入ConnectionUrl构造函数的参数为NULL.");
        } else {
            this.originalURL = url;
            this.putProp("_CONNECTTIMEOUT", String.valueOf(1000L));
            this.putProp("_IDLETIMEOUT", String.valueOf(10L));
            this.putProp("_AUTORECONNECT", String.valueOf(false));
            this.putProp("_AUTORECONNECT_SLEEPTIME", String.valueOf(1000L));
            this.putProp("_HEARBEAT", String.valueOf(false));
            this.putProp("_CONNECTIONNUM", String.valueOf(1));
            this.putProp("_TIMEOUT", String.valueOf(3000L));
            this.putProp("_MAXREADIDLETIME", "30");
            int firstSep = url.indexOf(":");
            if (firstSep <= 0) {
                throw new IllegalArgumentException("ConnectionUrl构造函数参数错误: " + url);
            } else {
                this.targetAddr = url.substring(0, firstSep);

                try {
                    this.targetAddrIP = InetAddress.getByName(this.targetAddr).getHostAddress();
                } catch (UnknownHostException e) {
                    throw new IllegalArgumentException(e);
                }
                int secondSep = url.indexOf("?");
                if (secondSep > 0) {
                    this.targetPort = Integer.parseInt(url.substring(firstSep + 1, secondSep));
                    String propsString = url.substring(secondSep + 1);
                    String[] props = propsString.split("&");

                    for (String prop : props) {
                        String[] propPair = prop.split("=");
                        this.putProp(propPair[0].toUpperCase(), propPair[1]);
                    }
                } else {
                    this.targetPort = Integer.parseInt(url.substring(firstSep + 1));
                }

                if (null == this.connectProps.get("_SERIALIZETYPE")) {
                    this.putProp("_SERIALIZETYPE", String.valueOf(1));
                }

                this.formatedURL = toURL(this.targetAddr, this.targetPort, this.connectProps);
                this.hashcode = this.formatedURL.hashCode();
            }
        }

    }

    private void putProp(String k,String v) {
        this.connectProps.put(k,v);
    }

    public String getTargetAddr() {
        return this.targetAddr;
    }

    public String getTargetAddrIP() {
        return this.targetAddrIP;
    }

    public int getTargetPort() {
        return this.targetPort;
    }

    public String getProperty(String propertyName) {
        return this.connectProps.get(propertyName.toUpperCase());
    }

    public TreeMap<String, String> getProperties() {
        return this.connectProps;
    }

    @Override
    public String toString() {
        return this.formatedURL;
    }

    public static String toURL(String ip, int port, Map<String,String> prop) {

        StringBuilder sb = new StringBuilder();
        sb.append(ip).append(":").append(port).append("?");
        for (Map.Entry<String,String> entry : prop.entrySet()) {
            sb.append((entry.getKey()).toUpperCase());
            sb.append("=");
            sb.append(entry.getValue());
            sb.append("&");
        }

        sb.deleteCharAt(sb.length() - 1);
        return sb.toString();
    }

    public static ConnectionUrl get(String url) {

        SoftReference<ConnectionUrl> softRef = parsedUrls.get(url);
        ConnectionUrl urlObject = softRef == null ? null : softRef.get();
        if (urlObject != null) {
            return urlObject;
        } else {
            ConnectionUrl returnObject = new ConnectionUrl(url);
            parsedUrls.put(url,new SoftReference<>(returnObject));
            return returnObject;
        }

    }

    public static long getConnectTimeout(String url) {
        ConnectionUrl urlObj = get(url);
        return Long.parseLong(urlObj.getProperty("_CONNECTTIMEOUT"));
    }

    public static long getReadTimeout(String url) {
        ConnectionUrl urlObj = get(url);
        return Long.parseLong(urlObj.getProperty("_TIMEOUT"));
    }

    public static int getIdleTimeout(String url) {
        ConnectionUrl urlObj = get(url);
        return Integer.parseInt(urlObj.getProperty("_IDLETIMEOUT"));
    }

    public static int getMaxReadIdle(String url) {
        ConnectionUrl urlObj = get(url);
        return Integer.parseInt(urlObj.getProperty("_MAXREADIDLETIME"));
    }

    public static byte getSerializeProtocol(String url) {
        ConnectionUrl urlObj = get(url);
        return Byte.parseByte(urlObj.getProperty("_SERIALIZETYPE"));
    }

    public static boolean isAutoReconnect(String url) {
        ConnectionUrl urlObj = get(url);
        return Boolean.parseBoolean(urlObj.getProperty("_AUTORECONNECT"));
    }

    public static InetSocketAddress socketAddr(String url) {

        ConnectionUrl targetUrl = get(url);
        return new InetSocketAddress(targetUrl.getTargetAddr(),targetUrl.getTargetPort());

    }

    static int cachedInstanceCount() {
        return parsedUrls.size();
    }

    static void clearCachedUrls() {
        parsedUrls.clear();
    }

    public static String replaceProperty(String url, String key, String value) {
        StringBuilder sb = new StringBuilder(url);
        int keyIdx = url.indexOf(key);
        int questionIdx = url.indexOf(63);
        if (keyIdx < 0) {
            sb.append(questionIdx < 0 ? "?" : "&");
            sb.append(key).append("=").append(value);
            return sb.toString();
        } else {
            int startIdx = keyIdx + key.length() + 1;
            int endIdx = url.indexOf(38, startIdx);
            if (endIdx < 0) {
                sb.replace(startIdx, url.length(), value);
            } else {
                sb.replace(startIdx, endIdx, value);
            }

            return sb.toString();
        }
    }

    public static boolean isLocalhost(String host) {
        return "127.0.0.1".equals(host) || "localhost".equalsIgnoreCase(host);
    }

    public static String localhostRealIP() {
        String realIP = null;

        try {
            realIP = InetAddress.getLocalHost().getHostAddress();
        } catch (Exception e) {
            e.printStackTrace();
        }

        return realIP;
    }

    @Override
    public int hashCode() {
        return this.hashcode;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        } else if (this == obj) {
            return true;
        } else if (this.getClass() != obj.getClass()) {
            return false;
        } else {
            ConnectionUrl other = (ConnectionUrl) obj;
            return this.toString().equals(other.toString());
        }
    }

    @Override
    protected void finalize() {
        isCollected = true;
        parsedUrls.remove(this.originalURL);
    }
}
